﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OS.Instructions;

namespace OS
{
    static class Channel3
    {
        private static bool busyFlag = false;                   // The boolean value for if Channel 3 is busy
        private static int timer = 0;                           // The timer containing the number of cycles the channel has been busy
        private static int timeNeeded = 2;                      // The time that Channel 3 takes to perform its tasks
        private static Buffer cachedBuffer = null;              // The cached buffer for the channel to operate on
        private static ProcessControlBlock cachedPCB = null;    // The cached PCB for the channel to operate on
        private static String cacheType = "";                   // The type of object that is in the cache

        /// <summary>
        /// Getter/setter for busyFlag
        /// </summary>
        public static bool BusyFlag
        {
            get { return busyFlag; }
            set { busyFlag = value; }
        }

        /// <summary>
        /// Getter/setter for timer
        /// </summary>
        public static int Timer
        {
            get { return timer; }
            set { timer = value; }
        }

        /// <summary>
        /// Getter/setter for cacheType
        /// </summary>
        public static String CacheType
        {
            get { return cacheType; }
            set { cacheType = value; }
        }

        /// <summary>
        /// Increments the timer
        /// </summary>
        public static void IncrementTimer()
        {
            timer++;
        }

        /// <summary>
        /// Resets the timer
        /// </summary>
        public static void ResetTimer()
        {
            timer = 0;
        }

        /// <summary>
        /// Takes in a buffers and stores it until the channel is ready
        /// </summary>
        /// <param name="buffer">The buffer to cache</param>
        public static void CacheBuffer(Buffer buffer)
        {
            cachedBuffer = buffer;
        }

        /*
        public static void CachePCB(ProcessControlBlock p)
        {
            cachedPCB = p;
        }*/

        /// <summary>
        /// Clears the cache
        /// </summary>
        public static void ClearCache()
        {
            cacheType = "";
            cachedBuffer = null;
            cachedPCB = null;
        }

        /// <summary>
        /// Getter/setter for timeNeeded
        /// </summary>
        public static int TimeNeeded
        {
            get { return timeNeeded; }
            private set { timeNeeded = value; }
        }

        /// <summary>
        /// Channel 3's part of the ouput spooling subroutine
        /// </summary>
        public static Buffer Channel3OutputSpooling(Track track)
        {
            
            Buffer buffer = QueueManager.ebQueue.Dequeue();//cachedBuffer;

            buffer.Data = "";

            for (int i = 0; i < track.Data.Length; i++)
            {
                buffer.Data += track.Data[i];
            }

            buffer.Type = Buffer.TYPE.OUTPUTFUL;

            String tmp = "";

            foreach (char c in track.Data)
            {
                tmp += c.ToString();
            }

            Trace.WriteToTrace(tmp + " from track " + track.TrackID + " stored in buffer.");
            Trace.WriteToTrace("Empty buffer is now an outputful buffer.");

            //return buffer
            return buffer;
        }

        public static void Channel3GD(Track track, Frame frame)
        {
            frame.Data = track.Data;
            frame.LastAccessed = EntryPoint.cycle;          
            QueueManager.ioQueue.Peek().d.RemoveAt(0);

            //release track
            Drum.ClearTrack(track.TrackID);

            Trace.WriteToTrace("Time Slice Counter reset to 0.");

            ProcessControlBlock p = QueueManager.ioQueue.Dequeue();
            QueueManager.readyQueue.Enqueue(p);

            Trace.WriteToTrace("Process " + p.groupID + " moved from the IO Queue to the Ready Queue.");
        }

        public static void Channel3PD(Track track, Frame frame)
        {
            track.Data = frame.Data;
            track.IsFree = false;

            //increment PCBs number of lines printed
            QueueManager.ioQueue.Peek().lines++;

            if (!QueueManager.ioQueue.Peek().terminationType.Equals(""))
            {
                ProcessControlBlock p = QueueManager.ioQueue.Dequeue();
                QueueManager.terminatedQueue.Enqueue(p);
                Trace.WriteToTrace("Process " + p.groupID + " moved from the IO Queue to the Terminate Queue.");
            }
            else
            {
                Trace.WriteToTrace("Time Slice Counter reset to 0.");
                ProcessControlBlock p = QueueManager.ioQueue.Dequeue();
                QueueManager.readyQueue.Enqueue(p);
                Trace.WriteToTrace("Process " + p.groupID + " moved from the IO Queue to the Ready Queue.");
            }
        }

        /// <summary>
        /// Swaps out a frame from memory and stores it in the drum.
        /// </summary>
        /// <param name="frame">The frame to be swapped out</param>
        /// <param name="freeTrack">The track to store the swapped out frame</param>
        /// <param name="newPage">The page of the new instuction</param>
        public static void Channel3SwapOut(Frame frame, Track freeTrack, int newPage)
        {
            Trace.WriteToTrace("Swapping out frame " + frame.ID);

            //write info from the victim frame to the given track
            freeTrack.Data = frame.Data;
            freeTrack.IsFree = false;
            frame.Data = null;

            Trace.WriteToTrace("Data from frame " + frame.ID + " written into track " + freeTrack.TrackID);
            Track track;

            //locate drum track with faulted page
            if (!QueueManager.swapQueue.Peek().TLB.ContainsKey(newPage))
            {
                String instr = QueueManager.swapQueue.Peek().register_IR[0].ToString() + QueueManager.swapQueue.Peek().register_IR[1].ToString();
                if (instr.Equals("GD") || instr.Equals("SR"))
                {
                    track = Drum.GetFreeTrack();
                }
                else
                {
                    //invalid page fault
                    ProcessControlBlock p = QueueManager.swapQueue.Dequeue();
                    p.terminationType = Termination.InvalidPageFault;
                    QueueManager.terminatedQueue.Enqueue(p);
                    return;
                }
            }
            else
            {
                 track = (Track)QueueManager.swapQueue.Peek().TLB[newPage];
            }

            int oldPage = -1;

            //find where the frame being swapped out was to update the TLB
            for (int i = QueueManager.swapQueue.Peek().register_PTR; i < QueueManager.swapQueue.Peek().register_PTR + 10; i++)
            {
                if (UserStorage.PhysicalMemory[i, 0] != null && UserStorage.PhysicalMemory[i, 0].Equals(frame.ID.ToString()))
                {
                    oldPage = i % 10;
                    break;
                }
            }

            if (oldPage != -1)
            {
                //update page table
                QueueManager.swapQueue.Peek().TLB[oldPage] = freeTrack;
                UserStorage.PhysicalMemory[QueueManager.swapQueue.Peek().register_PTR + oldPage, 0] = "&";
            }
            else
            {
                Console.WriteLine("Channel 3 SwapOut: Should never hit here.");
            }

            QueueManager.swapQueue.Peek().frames--;
            Trace.WriteToTrace("Process " + QueueManager.swapQueue.Peek().groupID + " now has " + QueueManager.swapQueue.Peek().frames + " frames.");

            Trace.WriteToTrace("Page table updated.");

            //swap in
            Channel3.Channel3SwapIn(track, frame, newPage);
        }

        public static void Channel3SwapIn(Track track, Frame frame, int page)
        {
            Trace.WriteToTrace("Swapping in track " + track.TrackID);

            //read drum track with faulted page in newly allocated frame
            frame.Data = track.Data;
            frame.LastAccessed = EntryPoint.cycle;

            Trace.WriteToTrace("Data from track " + track.TrackID + " swapped in to frame " + frame.ID);

            QueueManager.swapQueue.Peek().frames++;
            Trace.WriteToTrace("Process " + QueueManager.swapQueue.Peek().groupID + " now has " + QueueManager.swapQueue.Peek().frames + " frames.");

            Channel3.ClearCache();

            //free track
            Drum.ClearTrack(track.TrackID);

            try
            {
                //update page table
                QueueManager.swapQueue.Peek().TLB[page] = frame;
                UserStorage.PhysicalMemory[QueueManager.swapQueue.Peek().register_PTR + page, 0] = frame.ID.ToString();
                Trace.WriteToTrace("Page table updated.");

                Trace.WriteToTrace("Time Slice Counter reset to 0.");
                ProcessControlBlock pcb = QueueManager.swapQueue.Dequeue();
                QueueManager.readyQueue.Enqueue(pcb);
                Trace.WriteToTrace("Process " + pcb.groupID + " moved from the Swap Queue to the Ready Queue");
                UserStorage.WriteFrameToMemory(frame, frame.ID);
            }
            catch (KeyNotFoundException)
            {
                Console.WriteLine("ch3 swap in - bad");
            }
        }        

        /// <summary>
        /// Channel 3's part of the input spooling subroutine
        /// </summary>
        public static IFBBuffer Channel3InputSpooling(Track track)
        {
            IFBBuffer buffer = QueueManager.ifbQueue.Dequeue();

            //write given ifb on given track
            Drum.WriteToTrack(track, buffer.Data);
            Trace.WriteToTrace(buffer.Data + " written to track " + track.TrackID);

            Channel3.ClearCache();

            //return buffer
            return buffer;
        }
    }
}